08 Django 会话技术
理论上,一个用户的所有请求操作都应该属于同一个会话,而另一个用户的请求操作则应该属于另一个会话,二者不能混淆。Web 应用程序是使用 Http 协议传输数据的,Http 协议是无状态的协议,一旦数据交换完成,客户端与服务器端的连接就会关闭,再次交换数据需要重新建立连接。这意味着服务器无法从连接上跟踪会话。
一、Cookie
要跟踪会话,必须引入一种机制。Cookie 就是这样一种机制。它可以弥补 Http 协议无状态的不足。在 Session 出现之前,基本上所有的网站都采用 Cookie 来跟踪会话。
Cookie 实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用 response 向客户端浏览器颁发一个 Cookie。客户端浏览器会把 Cookie 保存起来。当浏览器再次请求该网站时,浏览器把请求的网址连同该 Cookie 一同提交给服务器。服务器检查该 Cookie,以此来辨认用户状态。服务器还可以根据需要修改 Cookie 的内容。
由于 Http 是一种无状态的协议 ,服务器单从网络连接上不能知道客户端身份,怎么办?就给客户端颁发一个通行证,每人一个,无论谁访问都必须携带自己的通行证。这样服务器就能从通行证上确认客户身份了。这就是 Cookie 的工作原理。
Cookie 本身由服务器生成,通过 response 将 Cookie 写到浏览器上,下一次访问,浏览器会根据不同的规则携带 Cookie 过来。

注意:Cookie 不能跨浏览器,一般不跨域。
1. 设置 Cookie
使用 response 设置
response.set_cookie(key, value[, max_age=None, expires=None])
max_age:指定 Cookie 过期时间,整数,单位为秒。默认值为 None,浏览器关闭失效
expires:指定过期时间,还支持 datetime 或 timedelta,可以指定一个具体时间日期。
expires=datetime.datetime(2030, 1, 1, 2, 3, 4) 或
d = datetime.datetime.now() + datetime.timedelta(days=10)
expires=d
注意:max_age 和 expires 两个选一个指定
response.set_cookie('username', username, max_age=10) 或
response.set_cookie('username', username, expires=d)
2. 获取 Cookie
使用 request 获取
request.COOKIES.get('username')
3. 删除 Cookie
使用 response 删除
response.delete_cookie('username')
4. Cookie 存储到客户端的优缺点
优点:减轻服务器端的压力,提高网站的性能
缺点:安全性不高。在客户端很容易被查看或破解用户会话信息
5. CSRF
CSRF ,Cross Site Request Forgery,跨站请求伪造。
CSRF 是指攻击者盗用了你的身份,以你的名义发送恶意请求。包括以你的名义发送邮件、消息,盗取账号,甚至于购买商品,虚拟货转账等等。
造成个人隐私泄露以及财产安全等问题。

防止 CSRF
Django 下的 CSRF 预防机制。Django 第一次响应来自某个客户端的请求时,会在服务器端随机生成一个 token,把这个 token 放在 Cookie 里,然后每次 POST 请求都会带上这个 token,这样就能避免被 CSRF 攻击。
在 POST 请求时,表单中添加 {% csrf_token %}
<form action="" method="POST">
{% csrf_token %}
...
</form>
6. 模拟用户登录
用户注册、登录、退出登录三个功能
(1)准备
新键 User 应用,并在 templates 目录中新建 index.html、register.html、login.html 三个文件,先对三个文件内容进行简单标识,如 <title>首页</title> 。
新建用户模型。在 /User/models.py 中新 UserModel 模型(用户模型),代码如下:
from django.db import models
# 用户模型
class UserModel(models.Model):
username = models.CharField(max_length=30, unique=True)
password = models.CharField(max_length=100)
age = models.IntegerField(default=18)
不用添加用户数据,可以从页面注册。
新键视图函数并配置路由。在 /User/views.py 中新建三个视图函数,实现首页、登录、注册三个页面的跳转。
首页。def index(request) 函数
from django.shortcuts import render, HttpResponse, redirect, reverse
def index(request):
return render(request, "index.html")
注册。def register(request) 函数
def register(request):
return render(request, "register.html")
登录。def login(request) 函数
def login(request):
return render(request, "login.html")
路由。 为了方便使用主路由,但需要指定路由名称,后边会反向解析。
# 项目同名目录下的 urls.py
from User.views import *
urlpatterns = {
# 首页
path("index/", index, name="index") # 127.0.0.1:8000/index/
path("", index) # 也是首页链接,实现的是域名直接显示首页,127.0.0.1:8000/ 即可显示首页
# 注册
path("register/", register, name="register")
# 登录
path("login/", login, name="login")
path("admin/", admin.site.urls)
}
(2)注册功能
修改 register.html 内容,添加注册表单信息。
<form action="" method="post"> {# 使用 POST 方法提表单,增强安全性 #}
{% csrf_token %} {# CSRF #}
<p>
<label>用户名:<input type="text" name="uname"></label>
</p>
<p>
<label>密码:<input type="password" name="passwd"></label>
</p>
<p>
<label>年龄:<input type="tepy" name="age"></label>
</p>
<p>
<button>注册</button>
</p>
</form>
修改 register 视图函数
def register(request):
if request.method == "GET":
return render(request, "register.html")
elif request.method == "POST":
# 接收前端提交的数据
uname = request.POST.get("uname")
passwd = request.POST.get("passwd")
age = request.POST.get("age")
# 先判断用户是否已经被注册过
users = UserModel.obejcts.filter(username=uname) # 这里使用的是 filter,结果是个集合,因为定义模型时,username 加了 unique 约束,如果用户已经存在,这个结果也是个集合,只不过只有一个元素。如果用户不存在,结果集是个空值。
if users.exists():
return HttpResponse("用户已经存在")
try:
# 实现注册功能,添加用户数据
# 上面已经判断过用户名是否存在,这里可以不用 try
user = UserModel()
user.username = uname
user.password = passwd
user.age = age
user.save()
except:
return HttpResponse(注册失败)
# 注册完成跳转到登录页面
return redirect(reverse("login"))
(3)登录功能
修改 login.html 内容,添加登录表单
<form action="" method="post">
{% csrf_token %}
<p>
<label>用户名:<input type="text" name="uname"></label>
</p>
<p>
<label>密码:<input type="password" name="passwd"></label>
</p>
<p>
<button>登录</button>
</p>
</form>
修改 login 视图函数
def login(request):
if request.method == "GET":
return render(request, "login.html")
elif request.method == "POST":
# 1. 接收前端提交的数据
uname = request.POST.get("uname")
passwd = request.POST.get("passwd")
# 2. 登录验证
users = UserModel.objects.filter(username=uname, password=passwd)
if user.exists():
# 获取当前登录的用户对象
user = users.first() # 就像之前说的一样,username 添加了 unique 约束,如果 filter 后找到用户,结果集中也只会有一个用户对象,所以直接用 first 取出即可
# 3. 设置 cookie
response = redirect(reverse("index"))
d = datatime.datetime.now() + datetime.timedelta(7)
response.set_cookie("userid", user.id, expires=d)
return response
还需要修改 index 视图函数和 index.html 内容。登录后和登录前首面内容显示不同,登录前还是登录后,使用 cookie 判断
修改 index 视图函数
def index(request):
# 使用 Cookie 判断登录
userid = reqeust.COOKIES.get("userid", 0) # COOKIES.get() 第一个参数是,当前网站给这个浏览器设置的 cookie 的名,第二参数是如果没有找到 cookie 名时的默认值
# 获取登录用户
user = UserModel.objects.filter(id=userid).first()
data = {
"user": user,
}
return render(request, "index.html", data)
修改 index.html
{% if user %}
当前登录的用户: {{ user.username }}
{% else %}
<a href="{% url 'login' %}">登录</a>
<a href="{% url 'register' %}">注册</a>
{% endif %}
(4)实现退出登录
退出登录,只是删除 cookie 并跳转到 index 页面,没有对应的 html 文件,所以只需要添加 logout 视图函数、配置路由,即可实现。
# 项目同名目录下的 urls.py 中 urlpatterns 添加 path
path("logout/", logout, name="logout"),
# User/views.py 添加 logout 视图函数
def logout(request):
# 先获取当前登录的所有响应信息
response = redirect(reverse("index"))
# 删除 cookie 即是退出登录
response.delete_cookie("userid")
return response
断续修改 index.html,把退出登录链接添加到登录后的页面。
{% if user %}
当前登录的用户: {{ user.username }} <br>
<a href="{% url "logout" %}">退出</a>
{% else %}
<a href="{% url 'login' %}">登录</a>
<a href="{% url 'register' %}">注册</a>
{% endif %}
二、Session
Session 服务器端会话技术,依赖于 cookie。
Django 中户用 Session
在 setting.py 文件中,INSTALLED_APPS 中添加 django.contrib.sessions,MIDDLEWARE 中添加 django.contrib.session.middleware.SessionMidleware,默认已经配置好了。
1. 设置 Sessions 值
使用 request 设置
request.session["user_id"] = user.id
request.session.set_expiry(86400) # 设置过期时间
2. 获取 Sessions 值
使用 request 设置
# get(key, defautl=None) # 根据键获取值
session_value = request.session.get("user_id")
# 或
session_value = request.session["user_id"]
3. 删除 Session 值
# 获取当前登录请求的 session 的 key
session_key = request.session.session_key
# 删除
del request.session[session_key]
# request.session.delete(session_key)
# 删除当前会话数据并删除会话的 cookie
flush()
数据存储到数据库中会进行编码,使用的是 Base64 。
每个 HttpRequest 对象都有一个 Session 属性,也是一个类字典对象。